/**
******************************************************************************************
* @file Connection.c
* @brief Management of the connection TCP/IP.
* @date 29 mars 2015
* @author Benjamin Lancinot
*****************************************************************************************/
/* COPYRIGHT ****************************************************************************
*****************************************************************************************
* Copyright (c) 2015, Audrey LOISEAU and Benjamin LANCINOT.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
*    list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
*    this list of conditions and the following disclaimer in the documentation
*    and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
********************************************************************************************/

#include "Connection.h"

/*******************************************************************************************
* PROTOTYPES
********************************************************************************************/

void Connection_destroyUtilities(Connection *);

void Connection_destroy(Connection **);

void Connection_start(Connection *);

void Connection_stop(Connection *);

int openSocket(Connection *);

int closeSocket(Connection *);

int checkConnections(Connection *);

int checkDeconnections(Connection *);

void unlinkObserverstoSubjects(Connection *, int);

void linkObserverstoSubjects(Connection *, int);

void * runConnection(Connection *);

/*******************************************************************************************
* FUNCTIONS
********************************************************************************************/

/**
 * @brief Constructor
 * @param nBB NannyBearBrain for the unregistration/registration of subjects
 * @return Pointer to a new Connection
 **/
Connection* Connection_create(NannyBearBrain* nBB){
        Connection* This = (Connection *) malloc(sizeof(*This));
        int i;
        This->nannyBearBrain = nBB;
        This->start = Connection_start;
        This->stop = Connection_stop;
        This->destroy = Connection_destroy;
        This->clientsNumber = -1;
        This->spreader = Spreader_create(This->proxyTab);

        for (i=0 ; i<MAX_CLIENTSOCKET;i++){
		This->clientSocket[i] =-1;
	}

	if (pthread_mutex_init(&This->mutex, NULL) != 0)
	{
		printf("\n mutex init failed\n");
	}

	return This;

}


/**
 * @brief Destroy all of the components of Connection
 * @param This Pointer to the wanted Connection
 **/
void Connection_destroyUtilities(Connection * This){
        int i;
	for(i=0; i<This->clientsNumber +1;i++){
                if(This->proxyTab[i] != NULL){
		This->proxyTab[i]->destroy(&This->proxyTab[i]);
		}
        }
	for(i=0; i<This->clientsNumber +1;i++){
                if(This->reception[i] != NULL){
		close(This->reception[i]->clientSocket);
		This->reception[i]->destroy(&This->reception[i]);
		}
	}

        //close(This->serverSocket);
}


/**
 * @brief Destructor
 * @param This Double pointer to the wanted Connection
 **/
void Connection_destroy(Connection ** This){

	Connection_destroyUtilities(* This);
	if (*This) {
		free(*This);
		*This = NULL;
	}
}


/**
 * @brief Run the thread which permit to launch the connection
 * @param This Pointer to the wanted Connection
 **/
void Connection_start(Connection *This){

	// The routine is available and the connection is establish
        pthread_mutex_lock(&This->mutex);
	This->carryOn = true;
        pthread_mutex_unlock(&This->mutex);
	This->state = INIT;
	// Creation of the thread which listen messages

	if (openSocket(This)){
        pthread_create(&This->postmanThread, 0,(void *) &runConnection,(Connection *) This);
        }else{
		perror("open SOCKET");
        }
}


/**
 * @brief Stop the thread which permit to launch the connection
 * @param This Pointer to the wanted Connection
 **/
void Connection_stop(Connection *This){
        int i;
        for(i=0; i<This->clientsNumber +1;i++){
                if(This->proxyTab[i] != NULL && This->proxyTab[i]->carryOn ==true){
		This->proxyTab[i]->stop(This->proxyTab[i]);
                This->reception[i]->stop(This->reception[i]);
		}
        }

        pthread_mutex_lock(&This->mutex);
        This->carryOn = false;
        pthread_mutex_unlock(&This->mutex);
        if (This->postmanThread){
		pthread_join(This->postmanThread,NULL);
                perror("in join");
        }

}



/**
 * @brief Creation of the socket and management of the listening
 * @param This Pointer to the wanted Connection
 * @return result of the opening socket
 **/
int openSocket(Connection *This){

	struct sockaddr_in myAddress;
// Creation of the socket : PF_INET = IP, SOCK_STREAM = TCP
	This->serverSocket = socket (PF_INET, SOCK_STREAM, 0);
	myAddress.sin_family = AF_INET; // adress type = IP
	myAddress.sin_port = htons (PORT_SERVER); // allocation of the good Port TCP
	myAddress.sin_addr.s_addr = htonl (INADDR_ANY); // Link all interfaces

// Link the socket to the adress
	if(bind (This->serverSocket, (struct sockaddr *)&myAddress, sizeof (myAddress))==0){
		//Listening the socket
		if(listen (This->serverSocket, MAX_PENDING_CONNECTIONS)==0){
		   	return TRUE;
		}else{
			perror("error listen()");

		}
	}else{
		perror("error bind()");
	}
	return FALSE;
}


/**
 * @brief Closing the socket
 * @param This Pointer to the wanted Connection
 * @return result of the closing socket
 **/
int closeSocket(Connection *This){

        int i;
	for(i=0; i<This->clientsNumber +1;i++){
		if (This->clientSocket[i] != -1){
			close(This->clientSocket[i]);
			perror("close socket");
		}
	}
	return 0;
}


/**
 * @brief  Active function for waiting and initializing connection with a client
 * @param This Pointer to the wanted Connection
 * @return The socket client or -1 if it doesn't exist
 **/
int checkConnections(Connection *This){
        fd_set readset;
        struct timeval tv;
        tv.tv_sec = 1;
        tv.tv_usec = 0;
        int retval=-1;

	FD_ZERO(&readset);
	FD_SET(This->serverSocket, &readset);

	retval = select(This->serverSocket +1,  &readset, NULL, NULL, &tv);
	if (retval>0){
		retval= accept (This->serverSocket, NULL, 0);
                printf(" socket client create ::::::> %d\n",retval);
        }
        return retval;
}


/**
 * @brief  Active function for deconnecting absent client
 * @param This Pointer to the wanted Connection
 * @return The id of the client or -1 if it doesn't exist
 **/
int checkDeconnections(Connection *This){
        int i=0;
        int retval =-1;
	while(i< This->clientsNumber+1 && retval == -1){
		if (This->reception[i]){
			pthread_mutex_lock(&This->reception[i]->mutex);
			if(This->reception[i]->carryOn == false){
				retval = i;
                                printf(" socket client deconnecter ::::::> %d\n",This->reception[i]->clientSocket);
			}
			pthread_mutex_unlock(&This->reception[i]->mutex);
		}
		i++;
        }
        return retval;

}

/**
 * @brief Remove the link between observers and subjects which refere to connection and NannyBearBrain
 * @param This Pointer to the wanted Connection
 * @param id Id of the client
 **/
void unlinkObserverstoSubjects(Connection *This, int id){
	if (This->proxyTab[id] != NULL){
		This->nannyBearBrain->electrician->unregisterObserver(This->nannyBearBrain->electrician, This->proxyTab[id]->observer);
		This->nannyBearBrain->mercureLover->unregisterObserver(This->nannyBearBrain->mercureLover, This->proxyTab[id]->observer);
		This->nannyBearBrain->humidityTester->unregisterObserver(This->nannyBearBrain->humidityTester, This->proxyTab[id]->observer);
		This->nannyBearBrain->soundEngineer->unregisterObserver(This->nannyBearBrain->soundEngineer, This->proxyTab[id]->observer);
		This->nannyBearBrain->mercureLover->mercureController->unregisterObserver(This->nannyBearBrain->mercureLover->mercureController, This->proxyTab[id]->observer);
		This->nannyBearBrain->humidityTester->humidityController->unregisterObserver(This->nannyBearBrain->humidityTester->humidityController, This->proxyTab[id]->observer);
		This->nannyBearBrain->mercureLover->averageMaker->unregisterObserver(This->nannyBearBrain->mercureLover->averageMaker, This->proxyTab[id]->observer);
		This->nannyBearBrain->humidityTester->averageMaker->unregisterObserver(This->nannyBearBrain->humidityTester->averageMaker, This->proxyTab[id]->observer);
	}
}



/**
 * @brief Save observers and subjects which refere to connection and NannyBearBrain
 * @param This Pointer to the wanted Connection
 * @param id Id of the client
 **/
void linkObserverstoSubjects(Connection *This, int id){
	//Link observers
	This->nannyBearBrain->electrician->registerObserver(This->nannyBearBrain->electrician, This->proxyTab[id]->observer);
	This->nannyBearBrain->mercureLover->registerObserver(This->nannyBearBrain->mercureLover, This->proxyTab[id]->observer);
	This->nannyBearBrain->humidityTester->registerObserver(This->nannyBearBrain->humidityTester, This->proxyTab[id]->observer);
	This->nannyBearBrain->soundEngineer->registerObserver(This->nannyBearBrain->soundEngineer, This->proxyTab[id]->observer);
	This->nannyBearBrain->mercureLover->mercureController->registerObserver(This->nannyBearBrain->mercureLover->mercureController, This->proxyTab[id]->observer);
	This->nannyBearBrain->humidityTester->humidityController->registerObserver(This->nannyBearBrain->humidityTester->humidityController, This->proxyTab[id]->observer);
	This->nannyBearBrain->mercureLover->averageMaker->registerObserver(This->nannyBearBrain->mercureLover->averageMaker, This->proxyTab[id]->observer);
	This->nannyBearBrain->humidityTester->averageMaker->registerObserver(This->nannyBearBrain->humidityTester->averageMaker, This->proxyTab[id]->observer);


	//notify observer for the first value
	This->nannyBearBrain->electrician->subject->notifyObservers(This->nannyBearBrain->electrician->subject);
	This->nannyBearBrain->mercureLover->subject->notifyObservers(This->nannyBearBrain->mercureLover->subject);
	This->nannyBearBrain->humidityTester->subject->notifyObservers(This->nannyBearBrain->humidityTester->subject);
	This->nannyBearBrain->soundEngineer->subject->notifyObservers(This->nannyBearBrain->soundEngineer->subject);
	This->nannyBearBrain->mercureLover->mercureController->subject->notifyObservers(This->nannyBearBrain->mercureLover->mercureController->subject);
	This->nannyBearBrain->humidityTester->humidityController->subject->notifyObservers(This->nannyBearBrain->humidityTester->humidityController->subject);
	This->nannyBearBrain->mercureLover->averageMaker->subject->notifyObservers(This->nannyBearBrain->mercureLover->averageMaker->subject);
	This->nannyBearBrain->humidityTester->averageMaker->subject->notifyObservers(This->nannyBearBrain->humidityTester->averageMaker->subject);
}


/**
 * @brief Thread which permitt to manage the life cycle of the connection
 * @param This Pointer to the wanted Connection
 * @return The final value for the thread
 **/
void * runConnection(Connection *This){
        int run = true;
        int connection_id=0;
        int retval;
        int i, connected=0;
	while(run){


		//State machine for the life cycle of the connection
		switch(This->state){
		case INIT:
			This->nannyBearBrain->connectionState->registerObserver(This->nannyBearBrain->connectionState, This->nannyBearBrain->lcdProjector->observer);
			This->nannyBearBrain->connectionState->changeStatus(This->nannyBearBrain->connectionState,"NO    ");
			This->state = CHECK_NEW_CONNECTION;
			break;
		case CHECK_NEW_CONNECTION:
                        retval = checkConnections(This);
			if (retval > 0){
				This->state = CREATE_NEW_CLIENT;
                        }else{
				This->state = CHECK_NEW_DECONNECTION;
			}
			break;
		case CHECK_NEW_DECONNECTION:
                        retval = checkDeconnections(This);
			if (retval >= 0){
				This->state = KILL_OLD_CLIENT;
                        }else{
				This->state = CHECK_NEW_CONNECTION;
			}
			break;
		case CREATE_NEW_CLIENT:
			connection_id =-1;
                        if(connected == 0){
                             This->nannyBearBrain->connectionState->changeStatus(This->nannyBearBrain->connectionState,"YES   ");
                        }
			connected++;
                        if (This->clientsNumber >-1){
				for (i=0;i<This->clientsNumber +1; i ++){
					if (This->reception[i]->clientSocket == retval){
						connection_id = i;
					}
				}
			}
                        if (connection_id == -1){
                                This->clientsNumber = This->clientsNumber +1;
				connection_id = This->clientsNumber;
                                This->proxyTab[connection_id] = ProxyTab_create();
			        This->reception[connection_id] = Reception_create(This->spreader,retval,This->nannyBearBrain->cmdTab, This->nannyBearBrain->cmdTabSize);
                        }

                        This->reception[connection_id]->start(This->reception[connection_id]);
			This->proxyTab[connection_id]->start(This->proxyTab[connection_id],retval);
                        linkObserverstoSubjects(This,connection_id);
                        This->spreader->addActiveProxy(This->spreader,connection_id);
			This->state = CHECK_NEW_DECONNECTION;
			break;
		case KILL_OLD_CLIENT:
                        connected--;
                        if(connected == 0){
                             This->nannyBearBrain->connectionState->changeStatus(This->nannyBearBrain->connectionState,"NO   ");
                        }
                        unlinkObserverstoSubjects(This,retval);
			This->proxyTab[retval]->stop(This->proxyTab[retval]);
                        This->reception[retval]->carryOn = true;
                        This->spreader->deleteProxy(This->spreader,retval);
                        This->state = CHECK_NEW_CONNECTION;
			break;
		default:
			break;
		}

	        pthread_mutex_lock(&This->mutex);
                if(This->carryOn == false){
                        run = false;
		}
                pthread_mutex_unlock(&This->mutex);

        	usleep(5000);
	}
	return NULL;
}

